//
//  InMemoryFileSystem.cpp
//  FuseSQLite
//
//  Created by Admin on 6/8/14.
//  Copyright (c) 2014 Admin. All rights reserved.
//

#include "InMemoryFileSystem.h"

#include "Utils.h"

InMemoryFileSystem::InMemoryFileSystem()
: m_root("/", true)
{}

bool InMemoryFileSystem::readDirectory(const char* directoryPath, std::vector<FileInfo>* children)
{
    children->clear();
    
    FileInfo node;
    node.name = ".";
    children->push_back(node);
    
    node.name = "..";
    children->push_back(node);
    
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(directoryPath, &pathComponents);
    FsTreeNode* fsNode = getNode(pathComponents);
    if (!fsNode)
        return false;
    
    for (auto childNode : fsNode->children)
    {
        node.name = childNode->name;
        node.stat = childNode->stat;
        children->push_back(node);
    }
    
    return true;
}

bool InMemoryFileSystem::createDirectory(const char* path, mode_t mode)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(path, &pathComponents);
    
    if (pathComponents.size() <= 1)
        return false;

    FsTreeNode* node = getParentNode(pathComponents);
    if (!node)
        return false;

    node->children.push_back(std::make_shared<FsTreeNode>(pathComponents.back().c_str(), true));
    
    return true;
}

bool InMemoryFileSystem::remove(const char* path)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(path, &pathComponents);
    FsTreeNode* parentNode = getParentNode(pathComponents);
    if (!parentNode)
        return false;
    
    for (int i = 0; i < parentNode->children.size(); ++i)
    {
        if (parentNode->children[i]->name == pathComponents.back())
        {
            auto childToErase = parentNode->children.begin() + i;
            parentNode->children.erase(childToErase);
            return true;
        }
    }
    
    return false;
}

bool InMemoryFileSystem::renameDirectory(const char* path, const char* newName)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(path, &pathComponents);
    
    FsTreeNode* node = getNode(pathComponents);
    if (!node)
        return false;

    std::vector<std::string> newPathComponents;
    Utils::splitPathIntoComponents(newName, &newPathComponents);
    
    node->name = newPathComponents.back();
    
    return true;
}

bool InMemoryFileSystem::getAttributes(const char* path, struct stat* stat)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(path, &pathComponents);
    
    FsTreeNode* node = getNode(pathComponents);
    if (!node)
        return false;
    
    node->stat.st_size = node->fileData ? node->fileData->size() : 0;
    
    *stat = node->stat;
    
    return true;
}

bool InMemoryFileSystem::createFile(const char* filePath, std::unique_ptr<InMemoryFile>* file)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(filePath, &pathComponents);
    
    if (pathComponents.size() <= 1)
        return false;
    
    FsTreeNode* node = getParentNode(pathComponents);
    if (!node)
        return false;
    
    auto newNode = std::make_shared<FsTreeNode>(pathComponents.back().c_str(), false);
    node->children.push_back(newNode);
    
    file->reset(new InMemoryFile(newNode->fileData.get()));
    
    return true;
}

bool InMemoryFileSystem::openFile(const char* filePath, std::unique_ptr<InMemoryFile>* file)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(filePath, &pathComponents);
    
    if (pathComponents.size() <= 1)
        return false;
    
    FsTreeNode* fileNode = getNode(pathComponents);
    if (!fileNode)
        return false;
    
    file->reset(new InMemoryFile(fileNode->fileData.get()));
    
    return true;
}

bool InMemoryFileSystem::truncateFile(const char* filePath, size_t newSize)
{
    std::vector<std::string> pathComponents;
    Utils::splitPathIntoComponents(filePath, &pathComponents);
    
    FsTreeNode* fileNode = getNode(pathComponents);
    if (!fileNode)
        return false;
    
    if (!fileNode->fileData)
        return false;
        
    fileNode->fileData->resize(newSize);
    
    return true;
}

#pragma mark - private

FsTreeNode* InMemoryFileSystem::getParentNode(const std::vector<std::string>& pathComponents)
{
    if (pathComponents.front() != "/")
        return nullptr;
    
    FsTreeNode* node = &m_root;

    for (int i = 1; i < pathComponents.size() - 1; ++i)
    {
        FsTreeNode* neededNode = nullptr;
        for (auto childNode : node->children)
        {
            if (childNode->name == pathComponents[i])
            {
                neededNode = childNode.get();
                break;
            }
        }
        
        if (!neededNode)
            return nullptr;
        
        node = neededNode;
    }

    return node;
}

FsTreeNode* InMemoryFileSystem::getNode(const std::vector<std::string>& pathComponents)
{
    if (pathComponents.front() != "/")
        return nullptr;
    
    FsTreeNode* node = &m_root;
    
    for (int i = 1; i < pathComponents.size(); ++i)
    {
        FsTreeNode* neededNode = nullptr;
        for (auto childNode : node->children)
        {
            if (childNode->name == pathComponents[i])
            {
                neededNode = childNode.get();
                break;
            }
        }
        
        if (!neededNode)
            return nullptr;
        
        node = neededNode;
    }
    
    return node;
}
